package p.ithorns.sample.moudle2;

import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPatternParser;
import p.ithorns.sample.ModulePath;

import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * ModulePathHandlerMapping
 *
 * @author ithorns
 * @version 1.0.0
 * @date 2021/10/26 15:51
 */
public class ModulePathHandlerMapping extends RequestMappingHandlerMapping {

    private final Map<String, ModulePathCondition> conditions = new ConcurrentHashMap<>(32);

    private String contextPath;

    @PostConstruct
    public synchronized void init() {
        WebApplicationContext context = getWebApplicationContext();
        ServletContext sc = Objects.requireNonNull(context).getServletContext();
        this.contextPath = Objects.requireNonNull(sc).getContextPath();
    }

    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        if (handlerType.equals(BasicErrorController.class)) {
            return new ModulePathCondition(ModuleConst.ERROR_PATH);
        }

        if (isCandidate(handlerType)) {
            // ApplicationContext context = getApplicationContext();
            // Object bean = Objects.requireNonNull(context).getBean(handlerType);
            ModulePath modulePath = handlerType.getAnnotation(ModulePath.class);
            if (null != modulePath) {
                String path = modulePath.path();
                ModulePathCondition condition = conditions.get(path);
                if (null == condition) {
                    condition = buildFrom(modulePath);
                    conditions.put(path, condition);
                }
                return condition;
            }
        }
        return null;
    }


    private boolean isCandidate(Class<?> clazz) {
        int modifiers = clazz.getModifiers();
        return !(Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers)) && clazz.isAnnotationPresent(ModulePath.class);
    }

    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        ModulePath annotation = AnnotationUtils.findAnnotation(method, ModulePath.class);
        if (null == annotation) {
            // 方法上没有，看类上是否有
            return getCustomTypeCondition(method.getDeclaringClass());
        }
        return buildFrom(annotation);
    }

    private ModulePathCondition buildFrom(ModulePath module) {
        return null == module
                ? new ModulePathCondition("/")
                : new ModulePathCondition(contextPath + module.path());
    }

    /**
     * 这里要解决一个问题:
     * 就是Swagger文档功能能正常访问(http://{host}:{port}/{context-path}/doc.html)，
     * 但就是无法显示接口列表信息(看起来像是没有扫描到Controller),
     *
     * 从原理和源码分析发现，由于{@link SwaggerBeanPostProcessor#customizeSpringfoxHandlerMappings}；
     * 对WebMvcRequestHandlerProvider的handlerMappings做了过滤：
     * filter: mapping.getPatternParser() == null
     * 导致所有controller的mapping没有加载到swagger去；
     *
     * 要解决这个问题，就是将patternParser设置为null；
     * （至于其他副作用暂时还未出现）
     *
     * 比较推荐的方案是从源头解决：
     * 由源码可知，setPatternParser方法在{@link WebMvcConfigurationSupport#requestMappingHandlerMapping}中调用，
     * 参考源码：
     * PathMatchConfigurer pathConfig = getPathMatchConfigurer();
     * 	if (pathConfig.getPatternParser() != null) {
     * 	    mapping.setPatternParser(pathConfig.getPatternParser());
     * }
     * 而触发这个设置的关键是配置了 spring.mvc.pathmatch.matching-strategy=path_pattern_parser;
     * 所以，将这个配置改成 ant_path_matcher 也可以解决这个问题，但前提是其他配置不需要强制使用path_pattern_parser策略
     *
     * 另外，springboot-2.6.x以后版本整合Swagger报错："this.condition" is null，也是由于设置成path_pattern_parser导致的
     * <a href="https://github.com/springfox/springfox/issues/3462"/>
     *
     * 其实默认RequestMappingHandlerMapping的patternParser值就是null，
     * 如果你设置matching-strategy成path_pattern_parser，那么，同样会出现swagger没有接口内容的问题
     *
     * Swagger原理参考docs文档
     */
    @Override
    public PathPatternParser getPatternParser() {
        // return super.getPatternParser();
        return null;
    }


}